package com.exer.dataload.log;

import com.exer.dataload.utils.Utils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * 日志类
 */
public class DorisDataLoadLog implements InvocationHandler {

    /**
     * 日志打印模板
     */
    private static final String LOG_TEMPLATE = "%s [%s][Class: %s, Method: %s, ThreadId: %s] message：%s \n";

    /**
     * 被代理对象
     */
    private final Object target;

    private DorisDataLoadLog(Object target) {
        this.target = target;
    }

    /**
     * 获取当前时间，并格式化为yyyy-MM-dd HH:mm:ss
     *
     * @return 格式化后的当前时间
     */
    private static String getTime() {
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String formattedDateTime = now.format(formatter);
        return formattedDateTime;
    }

    /**
     * 格式化streamLoad的返回结果
     * @param streamLoadResponse streamLoad的返回结果
     * @return 格式化后的结果
     */
    public static String formatStreamLoadResponse(String streamLoadResponse){
        StringBuilder stringBuilder = new StringBuilder("doris response is null");
        if (!"".equals(streamLoadResponse)) {
            stringBuilder.delete(0,stringBuilder.length());
            stringBuilder.append("Status: ")
                    .append(Utils.findSub(streamLoadResponse, "Status\":\\s*\"?([^\",]+)\"?,", 1)).append(", ")
                    .append("NumberTotalRows: ")
                    .append(Utils.findSub(streamLoadResponse, "NumberTotalRows\":\\s*\"?([^\",]+)\"?,", 1)).append(", ")
                    .append("NumberLoadedRows: ")
                    .append(Utils.findSub(streamLoadResponse, "NumberLoadedRows\":\\s*\"?([^\",]+)\"?,", 1)).append(", ")
                    .append("LoadTimeMs: ")
                    .append(Utils.findSub(streamLoadResponse, "LoadTimeMs\":\\s*\"?([^\",]+)\"?,", 1));
        }
        return stringBuilder.toString();
    }


    /**
     * 打印日志，会打印出代码执行到此的执行时间
     *
     * @param executor   线程池
     * @param className  类名
     * @param methodName 方法名
     * @param task       线程任务
     * @param <T>        线程任务泛型
     * @return Future<T>对象
     */
    public static <T> Future<T> submitWithLogging(ExecutorService executor, String className, String methodName, Callable<T> task) {
        return executor.submit(new LoggingCallable<>(task, className, methodName));
    }

    /**
     * 打印日志，会打印出代码执行到此的执行时间
     *
     * @param executor   线程池
     * @param className  类名
     * @param methodName 方法名
     * @param task       线程任务
     */
    public static void submitWithLogging(ExecutorService executor, String className, String methodName, Runnable task) {
        executor.submit(new LoggingRunnable(task, className, methodName));
    }

    /**
     * 打印日志，会打印出代码执行到此的执行时间
     *
     * @param executor   线程池
     * @param className  类名
     * @param methodName 方法名
     * @param task       线程任务
     * @param <T>        线程任务泛型
     * @return Future<T>对象
     */
    public static <T> Future<T> submitWithLogging(ExecutorService executor, String className, String methodName, String message, Callable<T> task) {
        return executor.submit(new LoggingCallable<>(task, className, methodName, message));
    }

    /**
     * 打印日志，会打印出代码执行到此的执行时间
     *
     * @param executor   线程池
     * @param className  类名
     * @param methodName 方法名
     * @param task       线程任务
     */
    public static void submitWithLogging(ExecutorService executor, String className, String methodName, String message, Runnable task) {
        executor.submit(new LoggingRunnable(task, className, methodName, message));
    }


    /**
     * 打印日志，会打印出代码执行到此的执行时间
     *
     * @param logType    日志类型
     * @param className  类名
     * @param methodName 方法名
     * @param message    额外信息
     */
    public static void log(LogType logType, String className, String methodName, String message) {
        if (logType == LogType.ERROR || logType == LogType.WARN) {
            System.err.printf(LOG_TEMPLATE, getTime(), logType, className, methodName, Thread.currentThread().getId(), message);
        } else {
            System.out.printf(LOG_TEMPLATE, getTime(), logType, className, methodName, Thread.currentThread().getId(), message);
        }
    }


    /**
     * 打印日志，会打印出代码执行到此的执行时间
     *
     * @param logType 日志类型
     * @param message 额外信息
     */
    public static void log(LogType logType, String message) {
        // 获取当前堆栈的第二项，即调用log方法的那行代码的堆栈信息
        StackTraceElement caller = new Exception().getStackTrace()[1];
        // 获取调用者所在的类名和方法名
        String className = caller.getClassName();
        int index = className.lastIndexOf(".");
        className = className.substring(index + 1);
        String methodName = caller.getMethodName();
        if (logType == LogType.ERROR || logType == LogType.WARN) {
            System.err.printf(LOG_TEMPLATE, getTime(), logType, className, methodName, Thread.currentThread().getId(), message);
        } else {
            System.out.printf(LOG_TEMPLATE, getTime(), logType, className, methodName, Thread.currentThread().getId(), message);
        }
    }


    /**
     * 传入毫秒数,格式化为xx小时xx分钟xx秒的形式
     *
     * @param milliseconds 毫秒数
     * @return 时分秒格式的字符串
     */
    public static String formatDuration(long milliseconds) {
        long hours = TimeUnit.MILLISECONDS.toHours(milliseconds);
        long minutes = TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60;
        long seconds = TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60;

        StringBuilder sb = new StringBuilder();

        if (hours > 0) {
            sb.append(hours).append("小时");
        }
        if (minutes > 0 || sb.length() > 0) { // 只有当有小时或分钟大于0时才输出分钟
            if (sb.length() > 0) sb.append(" ");
            sb.append(minutes).append("分钟");
        }
        if (seconds > 0 || sb.length() > 0) { // 当有秒数大于0或已经有小时/分钟时才输出秒数
            if (sb.length() > 0) sb.append(" ");
            sb.append(seconds).append("秒");
        }
        if (hours == 0 && minutes == 0 && seconds == 0) {
            sb.append(milliseconds).append("毫秒");
        }
        return sb.toString();
    }

    /**
     * 调用被代理对象
     *
     * @param proxy  代理对象
     * @param method 被代理的方法
     * @param args   被代理的参数
     * @return 被代理的方法的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        try {
            long startTime = System.currentTimeMillis();
            Object result = method.invoke(target, args);
            long endTime = System.currentTimeMillis();
            log(LogType.INFO, target.getClass().getSimpleName(), method.getName(), "执行完成,耗时:" + formatDuration(endTime - startTime));
            return result;
        } catch (Exception e) {
            log(LogType.ERROR, target.getClass().getSimpleName(), method.getName(), e.getMessage());
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取日志代理对象
     *
     * @param object          被代理对象
     * @param objectInterface 被代理对象接口
     * @param <T>             被代理对象接口类型
     * @return 代理对象
     */
    public static <T> T getLogProxyObject(Object object, Class<T> objectInterface) {
        if (!objectInterface.isInstance(object)) {
            throw new IllegalArgumentException("Provided object does not implement the given interface.");
        }
        return (T) Proxy.newProxyInstance(
                objectInterface.getClassLoader(),
                object.getClass().getInterfaces(),
                new DorisDataLoadLog(object));
    }

    /**
     * 私有内部类用于获取线程执行完成的时间
     */
    private static class LoggingRunnable implements Runnable {
        private final Runnable task;
        private final String className;
        private final String methodName;
        private String message;

        public LoggingRunnable(Runnable task, String className, String methodName) {
            this.task = task;
            this.className = className;
            this.methodName = methodName;
        }

        public LoggingRunnable(Runnable task, String className, String methodName, String message) {
            this.task = task;
            this.className = className;
            this.methodName = methodName;
            this.message = message;
        }

        @Override
        public void run() {
            try {
                long startTime = System.currentTimeMillis();
                task.run();
                long endTime = System.currentTimeMillis();
                if (this.message == null) {
                    log(LogType.INFO, className, methodName, "执行完成,耗时:" + formatDuration(endTime - startTime));
                } else {
                    log(LogType.INFO, className, methodName, this.message + ":执行完成,耗时:" + formatDuration(endTime - startTime));
                }
            } catch (Exception e) {
                log(LogType.ERROR, className, methodName, e.getMessage());
                e.printStackTrace();
            }
        }
    }

    /**
     * 私有内部类用于获取线程执行完成的时间
     */
    private static class LoggingCallable<T> implements Callable<T> {
        private final Callable<T> task;
        private final String className;
        private final String methodName;
        private String message;

        public LoggingCallable(Callable<T> task, String className, String methodName) {
            this.task = task;
            this.className = className;
            this.methodName = methodName;
        }

        public LoggingCallable(Callable<T> task, String className, String methodName, String message) {
            this.task = task;
            this.className = className;
            this.methodName = methodName;
            this.message = message;
        }

        @Override
        public T call() throws Exception {
            try {
                long startTime = System.currentTimeMillis();
                T result = task.call();
                long endTime = System.currentTimeMillis();
                if (this.message == null) {
                    log(LogType.INFO, className, methodName, "执行完成,耗时:" + formatDuration(endTime - startTime));
                } else {
                    log(LogType.INFO, className, methodName, this.message + ":执行完成,耗时:" + formatDuration(endTime - startTime));
                }
                return result;
            } catch (Exception e) {
                log(LogType.ERROR, className, methodName, e.getMessage());
                throw e;
            }
        }
    }

    /**
     * 日志类型枚举
     */
    public enum LogType {
        INFO,
        ERROR,
        DEBUG,
        WARN;
    }
}
